//
//  CLReadParser.m
//  CLBrowser
//
//  Created by 梁慧聪 on 2022/7/10.
//

#import "CLReadParser.h"
#import "CLBook.h"
#import <objc/runtime.h>
#import <objc/message.h>
#import <YYKit/YYClassInfo.h>
#import "CLCoreText.h"
@interface CLBookChapter : NSObject
@property (nonatomic, strong) CLBook *book;
@property (nonatomic, strong) CLReadChapterModel *chapter;
@end
@implementation CLBookChapter
@end
@interface CLReadParser ()
@property (nonatomic, strong) dispatch_queue_t queue;
@end
@implementation CLReadParser

SYNTHESIZE_SINGLETON_FOR_CLASS(CLReadParser);

- (instancetype)init {
    self = [super init];
    if (self) {
        NSString *name = [NSString stringWithFormat:@"com.cl.synchronizationqueue-%@", [[NSUUID UUID] UUIDString]];
        _queue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);
        [self readCache];
    }
    return self;
}

- (void)readCache {
    NSArray *books = [CLBook searchWithWhere:@"bookid!=''"];
    NSMutableArray *tmp = [NSMutableArray array];
    for (CLBook *book in books) {
        if ([book isValid]) {
            [tmp addObject:book];
        }
    }
    self.books = tmp;
}

- (NSArray<CLBook *>*)booksWithWhere:(NSString *)where {
    NSMutableArray *tmp = [NSMutableArray array];
    NSArray<CLBook *> *books = [CLBook searchWithWhere:where];
    for (CLBook *book in books) {
        book.isTemp = YES;
        [tmp addObject:[self bookById:book.bookid]];
    }
    return tmp;
}
- (NSArray<CLReadChapterModel *>*)chaptersWithWhere:(NSString *)where {
    NSMutableArray *tmp = [NSMutableArray array];
    NSArray<CLReadChapterModel *> *chapters = [CLReadChapterModel searchWithWhere:where];
    for (CLReadChapterModel *chapter in chapters) {
        chapter.isTemp = YES;
        [tmp addObject:[self chapterById:chapter.chapterid]];
    }
    return tmp;
}
- (CLBook *)bookById:(NSString *)bookid {
    if (NULLString(bookid)) {
        return nil;
    }
    CLBook *book = [self.books cl_firstFiltered:^BOOL(CLBook *b) {
        return [b.bookid isEqualToString:bookid];
    }];
    if (!book) {
        NSArray *books = [CLBook searchWithWhere:[NSString stringWithFormat:@"bookid='%@'",bookid]];
        book = [books cl_firstFiltered:^BOOL(CLBook *b) {
            return [b.bookid isEqualToString:bookid];
        }];
        JSLog(@"find cache book:<%p>%@",book,book.bookid);
    } else {
        JSLog(@"find book:<%p>%@",book,book.bookid);
    }
    return book;
}

- (CLReadChapterModel *)chapterById:(NSString *)chapterid {
    if (NULLString(chapterid)) {
        return nil;
    }
    return [self bookChapterById:chapterid].chapter;
}

- (CLBookChapter *)bookChapterById:(NSString *)chapterid {
    if (NULLString(chapterid)) {
        return nil;
    }
    CLBookChapter *bChapter = [[CLBookChapter alloc]init];
    for (CLBook *book in self.books) {
        for (CLReadChapterModel *c in book.catalogue) {
            if ([c.chapterid isEqualToString:chapterid]) {
                bChapter.book = book;
                bChapter.chapter = c;
                JSLog(@"find chapter:<%p>%@",c,c.chapterid);
                break;
            }
        }
    }
    
    if (!bChapter.chapter) {
        NSArray *chapters = [CLReadChapterModel searchWithWhere:[NSString stringWithFormat:@"chapterid='%@'",chapterid]];
        bChapter.chapter = chapters.firstObject;
//        JSLog(@"find cache chapter:<%p>%@",bChapter.chapter,bChapter.chapter.chapterid);
    }
    return bChapter;
}

- (CLBook *)saveToDBWithBookInfo:(NSDictionary *)bookInfo {
    if (![bookInfo isKindOfClass:[NSDictionary class]]) {
        return nil;
    }
    NSString *bookid = [bookInfo cl_stringForKey:@"bookid"];
    CLBook *cache = nil;
    @synchronized (self) {
        cache = [self bookById:bookid];
        if (cache) {
            //save
            unsigned int count;// 记录属性个数
            objc_property_t *properties = class_copyPropertyList([CLBook class], &count);
            if (properties) {
                for (unsigned int i = 0; i < count; i++) {
                    objc_property_t property = properties[i];
                    
                    YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:property];
                    if (info.name) {
                        void *pointer = ((void* (*)(id, SEL))(void *) objc_msgSend)(cache, info.getter);
                        if ([info.cls isSubclassOfClass:[NSString class]]) {
                            NSString *oldproperty = [NSString stringWithFormat:@"%@",pointer];
                            NSString *newproperty = [bookInfo cl_stringForKey:info.name];
                            if (NULLString(oldproperty) && !NULLString(newproperty)) {
                                [cache setValue:newproperty forKey:info.name];
                                JSLog(@"update book.%@:%@",info.name,newproperty);
                            }
                        } else if ([info.name isEqualToString:@"catalogue"]) {
                            NSArray *catalogue = [bookInfo cl_arrayForKey:info.name];
                            NSMutableArray *tmp = [NSMutableArray array];
                            for (NSDictionary *chapterInfo in catalogue) {
                                [tmp addObject:[self saveToDBWithChapterInfo:chapterInfo]];
                            }
                            if (tmp.count > 0 && cache.catalogue.count < tmp.count) {
                                cache.catalogue = tmp;
                                JSLog(@"update book.catalogue:%ld",tmp.count);
                            }
                        }
                    }
                }
                free(properties);
            }
        } else {
            //new
            cache = [CLBook modelWithDictionary:bookInfo];
            [self.books insertObject:cache atIndex:0];
        }
        if ([cache isValid]) {
            JSLog(@"book updateDB:<%p>%@,%ld",cache,cache.bookid,cache.catalogue.count);
            [cache saveToDB];
        } else {
            NSAssert3(0,@"book inValid:<%p>%@,%ld",cache,cache.bookid,cache.catalogue.count);
        }
    }
    return cache;
}

- (CLReadChapterModel *)saveToDBWithChapterInfo:(NSDictionary *)chapterInfo {
    if (![chapterInfo isKindOfClass:[NSDictionary class]]) {
        return nil;
    }
    NSString *chapterid = [chapterInfo cl_stringForKey:@"chapterid"];
    CLReadChapterModel *cache = nil;
    @synchronized (self) {
        CLBookChapter *bookChapter = [self bookChapterById:chapterid];
        cache = bookChapter.chapter;
        if (cache) {
            unsigned int count;
            objc_property_t *properties = class_copyPropertyList([CLReadChapterModel class], &count);
            if (properties) {
                for (unsigned int i = 0; i < count; i++) {
                    objc_property_t property = properties[i];
                    YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:property];
                    if (info.name) {
                        void *pointer = ((void* (*)(id, SEL))(void *) objc_msgSend)(cache, info.getter);
                        if ([info.cls isSubclassOfClass:[NSString class]]) {
                            NSString *oldproperty = [NSString stringWithFormat:@"%@",pointer];
                            NSString *newproperty = [chapterInfo cl_stringForKey:info.name];
                            if (NULLString(oldproperty) && !NULLString(newproperty)) {
                                [cache setValue:newproperty forKey:info.name];
                                JSLog(@"update chapter.%@:%@",info.name,newproperty);
                            }
                        }
                    }
                }
                free(properties);
            }
        } else {
            cache = [CLReadChapterModel modelWithDictionary:chapterInfo];
        }
        if ([cache isValid]) {
            [cache saveToDB];
        } else {
            NSAssert3(0,@"chapter inValid:<%p>%@,%ld",cache,cache.chapterid,cache.content.length);
        }
    }
    return cache;
}
// MARK: -- 内容分页
+ (NSArray <CLReadPageModel *>*)pageingWithAttrString:(NSAttributedString *)attrString rect:(CGRect)rect isFirstChapter:(BOOL)isFirstChapter {
    NSMutableArray<CLReadPageModel *> *pageModels = [NSMutableArray array];
    if (isFirstChapter) { // 第一页为书籍页面
        CLReadPageModel *pageModel = [[CLReadPageModel alloc]init];
        pageModel.range = NSMakeRange(CL_READ_BOOK_HOME_PAGE, 1);
        pageModel.contentSize = CL_READ_VIEW_RECT().size;
        [pageModels addObject:pageModel];
    }
    
    NSArray<NSValue *> *ranges = [CLCoreText getPageingRanges:attrString rect:rect];
    
    if (ranges && !ranges.isEmpty) {
        
        NSUInteger count = ranges.count;
        
        for (int i = 0; i < count; i++) {
            
            NSRange range = [ranges[i] rangeValue];
            
            CLReadPageModel *pageModel = [[CLReadPageModel alloc]init];
            
            NSAttributedString *content = [attrString attributedSubstringFromRange:range];
            
            pageModel.range = range;
            
            pageModel.content = content;
            
            pageModel.page = i;
            
            // --- (滚动模式 || 长按菜单) 使用 ---
            
            // 注意: 为什么这些数据会放到这里赋值，而不是封装起来， 原因是 contentSize 计算封装在 pageModel内部计算出现宽高为0的情况，所以放出来到这里计算，原因还未找到，但是放到这里计算就没有问题。封装起来则会出现宽高度不计算的情况。
            
            // 内容Size (滚动模式 || 长按菜单)
            CGFloat maxW = CL_READ_VIEW_RECT().size.width;
            
            pageModel.contentSize = CGSizeMake(maxW, [CLCoreText getAttrStringHeight: content maxW:maxW]);
            
            
            // 当前页面开头是什么数据开头 (滚动模式)
            if (i == 0) {
                pageModel.headType = CLPageHeadTypeChapterName;
            }else if ([content.string hasPrefix:CL_READ_PH_SPACE]) {
                pageModel.headType = CLPageHeadTypeParagraph;
            }else{
                pageModel.headType = CLPageHeadTypeLine;
            }
            
            
            // 根据开头类型返回开头高度 (滚动模式)
            switch (pageModel.headType) {
                case CLPageHeadTypeChapterName:
                    pageModel.headTypeHeight = 0;
                    break;
                case CLPageHeadTypeParagraph:
                    pageModel.headTypeHeight = [CLReadConfigure.sharedInstance paragraphSpacing];
                    break;
                case CLPageHeadTypeLine:
                default:
                    pageModel.headTypeHeight = [CLReadConfigure.sharedInstance lineSpacing];
                    break;
            }
            // --- (滚动模式 || 长按菜单) 使用 ---
            
            [pageModels addObject:pageModel];
        }
    }
    
    return pageModels;
}

// MARK: -- 内容整理排版

/// 内容排版整理
///
/// - Parameter content: 内容
/// - Returns: 整理好的内容
+ (NSString *)contentTypesetting:(NSString *)content {
    
    // 替换单换行
    content = [content stringByReplacingOccurrencesOfString:@"\r" withString:@""];
    // 替换换行 以及 多个换行 为 换行加空格
    content = [content replacingCharacters:@"\\s*\\n+\\s*" withString:[NSString stringWithFormat:@"\n%@",CL_READ_PH_SPACE]];
    
    // 返回
    return content;
}

// MARK: -- 解码URL

/// 解码URL
///
/// - Parameter url: 文件路径
/// - Returns: 内容
+ (NSString *)encode:(NSURL *)url {
    
    NSString *content = @"";
    
    if (url && url.absoluteString &&url.absoluteString.isEmpty) { return content; }
    
    // utf8
    content = [self encode:url encoding:NSUTF8StringEncoding];
    
    // 进制编码
    if (NULLString(content)) { content = [self encode:url encoding:0x80000632]; }
    
    if (NULLString(content)) { content = [self encode:url encoding:0x80000631]; }
    
    if (NULLString(content)) { content = @""; }
    
    return content;
}

/// 解析URL
///
/// - Parameters:
///   - url: 文件路径
///   - encoding: 进制编码
/// - Returns: 内容
+ (NSString *)encode:(NSURL *)url encoding:(NSStringEncoding)encoding {
    
    @try {
        NSError *error = nil;
        
        NSString *content = [NSString stringWithContentsOfURL:url usedEncoding:&encoding error:&error];
        
        if (error) {
            NSLog(@"解析URL失败:%@",error);
        }
        
        return StrNilHolder(content, @"");
    } @catch(NSException *e){
        NSLog(@"解析URL失败:%@",e);
    }
    
    return @"";
}
@end
